<?php

namespace proxyPattern;

/**
 * 代理模式的具体角色和职责
 * 1. 抽象主体结构：定义了真是对象和代理对象都需要实现的方法
 * 2. 真实主题（RealSubject）：也就是目标对象，真正去执行业务逻辑的类
 * 3. 代理类（Proxy）：实现和目标对象相同的接口，在内部维护一个真实对象的引用。代理类会控制真是对象的访问，可能会在调用前后加点特殊的处理
 */

// 以网络图片懒加载为例，我们用代理模式实现一个简单的图片加载系统

// 1. 抽象主体结构，定义抽象的图片接口。实际对象和代理对象都需要实现这个接口
interface Image
{
    public function display(): void;
}

// 2. 真实主题（RealSubject）：也就是目标对象，真正去执行业务逻辑的类
// 实现真是图片类，表示真正需要加载的图片资源
// 个人理解：在不改变原有类的情况下，设计一个代理类实现原对象的同一个接口，在重写的相同方法体中调用被代理对象的该方法，并加入额外的业务逻辑。实现对现有类的访问控制
class RealImage implements Image
{
    private string $fileName;

    public function __construct(string $fileName)
    {
        $this->fileName = $fileName;
        $this->loadFromDisk();
    }

    private function loadFromDisk(): void
    {
        echo "正在从磁盘加载图片：{$this->fileName}\n";
    }

    public function display(): void
    {
        echo "显示图片：{$this->fileName}\n";
    }
}

// 3. 代理类（Proxy）：实现和目标对象相同的接口，在内部维护一个真实对象的引用。代理类会控制真是对象的访问，可能会在调用前后加点特殊的处理
// 实现代理类，控制RealImage对象的创建和访问时机
class ProxyImage implements Image
{
    private string $fileName;
    private RealImage $realImage;

    public function __construct(string $fileName)
    {
        $this->fileName = $fileName;
    }

    // 在代理类中重写相同方法，并调用真实对象方法。然后在这前后增加额外的逻辑。这里就是延迟加载、缓存了原始图片对象
    public function display(): void
    {
        // TODO 例如这里还可以加一些鉴权的逻辑，这样真实的对象只负责显示图片的业务逻辑，不需要关心权限。
        if (empty($this->realImage)) {
            $this->realImage = new RealImage($this->fileName);
        }
        $this->realImage->display();
        // TODO 例如这里还可以加一些日志的逻辑，真实的对象就只负责显示图片的业务逻辑，不需要关心日志记录。
    }
}

// 测试
$image = new ProxyImage('test_image.jpg');
echo "第一次图片显示\n";
$image->display();
echo "第二次图片显示\n";
$image->display();